home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Sprite 1984 - 1993
/
Sprite 1984 - 1993.iso
/
src
/
kernel
/
rpc
/
rpcCall.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-12-18
|
25KB
|
839 lines
/*
* rpcCall.c --
*
* These are the top-level routines for the client side of Remote
* Procedure Call - the routines do overhead tasks like setting up a
* message, and managing client channels. The network protocol for
* the client side is in rpcClient.c.
*
* Copyright 1985 Regents of the University of California
* All rights reserved.
*/
#ifndef lint
static char rcsid[] = "$Header: /cdrom/src/kernel/Cvsroot/kernel/rpc/rpcCall.c,v 9.18 92/12/13 18:21:27 mgbaker Exp $ SPRITE (Berkeley)";
#endif /* not lint */
#include <sprite.h>
#include <stdio.h>
#include <rpc.h>
#include <rpcPacket.h>
#include <rpcClient.h>
#include <rpcTrace.h>
#include <rpcHistogram.h>
#include <sys.h>
#include <timerTick.h>
#include <timer.h>
#include <user/limits.h>
/* Not needed if recov tracing is removed. */
#include <recov.h>
/*
* So we can print out rpc names we include the rpcServer definitions.
*/
#include <rpcServer.h>
/*
* The client channel table is kept as an array of pointers to channels.
* rpcClient.h describes the contents of a ClientChannel. The number
* of channels limits the parallelism available on the client. During
* system shutdown, for example, there may be many processes doing
* remote operations (removing swap files) at the same time.
*/
RpcClientChannel **rpcChannelPtrPtr = (RpcClientChannel **)NIL;
int rpcNumChannels = 8;
int numFreeChannels = 8;
/*
* The allocation and freeing of channels is monitored.
* A process might have to wait for a free RPC channel.
*/
Sync_Condition freeChannels;
Sync_Semaphore rpcMutex = Sync_SemInitStatic("Rpc:rpcMutex");
/*
* There is sequence of rpc transaction ids that increases over time.
*/
unsigned int rpcID = 1;
/*
* A boottime Id is used to help servers realize that a client has
* rebooted since it talked to it last. It is zero for the first
* RPC, one that gets the time. Then it is set to that time and does
* not change until we reboot.
*/
unsigned int rpcBootID = 0;
/*
* A count is kept of the number of RPCs made.
* The zero'th element is used to count attempts at
* RPCs with unknown RPC numbers - RPC number 0 is unused.
*/
int rpcClientCalls[RPC_LAST_COMMAND+1];
/*
* For one of the client policies for handling negative acknowledgements,
* we ramp down the number of channels used with the ailing server. These
* data structures keep track of which servers are unhappy.
*/
typedef struct UnhappyServer {
int serverID;
Timer_Ticks time;
} UnhappyServer;
UnhappyServer serverAllocState[8];
/* Initial back-off interval for negative acknowledgements. */
unsigned int channelStateInterval;
/* forward declaration */
static Boolean GetChannelAllocState _ARGS_((int serverID, Timer_Ticks *time));
static void SetChannelAllocState _ARGS_((int serverID, Boolean trouble));
static void SetChannelAllocStateInt _ARGS_((int serverID, Boolean trouble));
/*
*----------------------------------------------------------------------
*
* Rpc_Call --
*
* Top-level interface for a client that makes a remote procedure
* call. Our caller has to pre-allocate all storage for the data
* going to the server in the request message, and for the data
* returning in the reply message. The storage argument contains
* pointers and sizes for this preallocated space. Upon return
* the data from the reply message will be in the specified storage
* areas.
*
* Results:
* An error code that either reflects an error in delivery/transport
* or is an error code from the remote procedure. Also, the storage
* input specification is modified - the return parameter and data
* size fields are updated to reflect the true size of the return
* parameter and data blocks. Finally, the return parameter and data
* areas contain the results of the remote procedure call.
*
* Side effects:
* There are no side effects on this machine except that some addressing
* information is kept as a hint for future RPCs. The semantics of
* the remote procedure are unlimited on the server machine.
*
*----------------------------------------------------------------------
*/
ReturnStatus
Rpc_Call(serverID, command, storagePtr)
int serverID; /* Indicates the server for the RPC. The
* special value SERVER_BROADCAST is used
* to specify a broadcast RPC. The first
* reply received is returned and subsequent
* replies get discarded. Broadcast RPCs
* are NOT retried if there is no response. */
int command; /* Rpc command. Values defined in rpcCall.h */
Rpc_Storage *storagePtr; /* Specifies buffer areas for request and
* reply messages. */
{
register RpcClientChannel *chanPtr; /* Handle for communication channel */
register ReturnStatus error; /* General error return status */
Time histTime; /* Time for histogram taking */
unsigned int srvBootID; /* Boot time stamp from server, used to
* track server reboots */
Boolean notActive = 0; /* Not active flag from server */
unsigned int recovType = 0; /* Whether rpc reply had recov flags. */
if (serverID < 0 || serverID >= NET_NUM_SPRITE_HOSTS) {
printf("Rpc_Call, bad serverID <%d>\n", serverID);
return(GEN_INVALID_ARG);
} else if (serverID != RPC_BROADCAST_SERVER_ID &&
serverID == rpc_SpriteID) {
printf("Rpc_Call: Trying RPC #%d to myself\n", command);
return(GEN_INVALID_ARG);
} else if ((serverID == RPC_BROADCAST_SERVER_ID) &&
! (command == RPC_FS_PREFIX ||
command == RPC_GETTIME)) {
panic("Trying to broadcast a non-prefix RPC");
return(GEN_INVALID_ARG);
}
#ifdef TIMESTAMP
RPC_NIL_TRACE(RPC_CLIENT_A, "Rpc_Call");
#endif /* TIMESTAMP */
allocAgain:
RPC_CALL_TIMING_START(command, &histTime);
chanPtr = RpcChanAlloc(serverID);
#ifdef TIMESTAMP
RPC_NIL_TRACE(RPC_CLIENT_B, "alloc");
#endif /* TIMESTAMP */
/*
* Initialize the RPC request message header and put buffer
* specifications of our caller into the state of the channel. This
* is once-per-rpc initialization that does not need to be repeated if
* we have to re-send.
*/
RpcSetup(serverID, command, storagePtr, chanPtr);
/*
* Update a histogram of RPCs made. The zeroth element is used to
* count unknown rpcs
*/
if (command > 0 &&
command <= RPC_LAST_COMMAND) {
rpcClientCalls[command]++;
} else {
/*
*/
printf("Rpc_Call: unknown rpc command (%d)\n", command);
rpcClientCalls[0]++; /* 0 == RPC_BAD_COMMAND */
}
#ifdef TIMESTAMP
RPC_NIL_TRACE(RPC_CLIENT_C, "setup");
#endif /* TIMESTAMP */
/*
* Call RpcDoCall, which synchronizes with RpcClientDispatch,
* to do the send-receive-timeout loop for the RPC.
*/
/* Remove this debugging print stuff soon. */
if (command == RPC_ECHO_2 && recov_PrintLevel >= RECOV_PRINT_ALL) {
Sys_HostPrint(serverID, "Pinging server\n");
}
error = RpcDoCall(serverID, chanPtr, storagePtr, command,
&srvBootID, ¬Active, &recovType);
RpcChanFree(chanPtr);
#ifdef TIMESTAMP
RPC_NIL_TRACE(RPC_CLIENT_OUT, "return");
#endif /* TIMESTAMP */
RPC_CALL_TIMING_END(command, &histTime);
/* This slow printing stuff should be removed soon. It's for debugging. */
if (command == RPC_ECHO_2 && recov_PrintLevel >= RECOV_PRINT_ALL) {
if (error == RPC_NACK_ERROR) {
Sys_HostPrint(serverID,
"Ping result bad: Nack error from server\n");
} else if (error == RPC_TIMEOUT || error == NET_UNREACHABLE_NET) {
Sys_HostPrint(serverID, "Ping result bad: server is dead.\n");
}
if (error == SUCCESS) {
Sys_HostPrint(serverID, "Pinged serverID successfully.\n");
}
}
if (error == RPC_NACK_ERROR) {
/*
* This error is only returned if the client policy for handling
* negative acknowledgements is to ramp down the number of channels
* used, so that's what we do.
*/
SetChannelAllocState(serverID, TRUE);
goto allocAgain;
}
#ifndef NO_RECOVERY
if (error == RPC_TIMEOUT || error == NET_UNREACHABLE_NET) {
if (command != RPC_ECHO_2) {
printf("<%s> ", rpcService[command].name);
Sys_HostPrint(serverID, "RPC timed-out\n");
}
Recov_HostDead(serverID);
} else {
Recov_HostAlive(serverID, srvBootID, TRUE, notActive, recovType);
}
#endif /* NO_RECOVERY */
return(error);
}
/*
*----------------------------------------------------------------------
*
* RpcSetup --
*
* Initialize the RPC header for the request message and set up the
* buffer specifications for the request and reply messages. The
* operations done here are done once before the request message is
* sent out the first time, and they do not have to be re-done if the
* request message needs to be re-sent.
*
* Results:
* None.
*
* Side effects:
* The Rpc header is initialized. The buffer specifications for the
* request and reply messages are set up.
*
*----------------------------------------------------------------------
*/
void
RpcSetup(serverID, command, storagePtr, chanPtr)
int serverID; /* The server for the RPC */
int command; /* The RPC to perform */
register Rpc_Storage *storagePtr; /* Specifies storage for the RPC
* parameters */
register RpcClientChannel *chanPtr; /* The channel for the RPC */
{
register Net_ScatterGather *bufferPtr; /* This specifies a part of the
* packet to the network driver
*/
register RpcHdr *rpcHdrPtr; /* The RPC header */
/*
* Initialize the RPC header for the request message. A couple fields
* are set up elsewhere. The server hint is left over from previous
* RPCs. The channel ID and the version number are set up at
* boot time by Rpc_Init.
*/
rpcHdrPtr = (RpcHdr *) &chanPtr->requestRpcHdr;
if(command == RPC_ECHO_1) {
rpcHdrPtr->flags = RPC_ECHO;
} else {
rpcHdrPtr->flags = RPC_REQUEST;
}
rpcHdrPtr->flags |= RPC_SERVER;
rpcHdrPtr->clientID = rpc_SpriteID;
rpcHdrPtr->serverID = serverID;
rpcHdrPtr->bootID = rpcBootID;
rpcHdrPtr->ID = rpcID++;
rpcHdrPtr->command = command;
/*
* Setup the timeout parameters depending on the route to the server.
* This is rather simple minded.
*/
if (chanPtr->constPtr == (RpcConst *)NIL) {
Net_Route *routePtr = Net_IDToRoute(serverID, -1, FALSE,
(Sync_Semaphore *) NIL, 0);
if (routePtr != (Net_Route *)NIL &&
routePtr->protocol == NET_PROTO_INET) {
chanPtr->constPtr = &rpcInetConst;
} else {
chanPtr->constPtr = &rpcEtherConst;
}
if (routePtr != (Net_Route *)NIL) {
Net_ReleaseRoute(routePtr);
}
}
/*
* Copy buffer pointers into the state of the channel.
*/
bufferPtr = &chanPtr->request.paramBuffer;
bufferPtr->bufAddr = storagePtr->requestParamPtr;
bufferPtr->length = storagePtr->requestParamSize;
rpcHdrPtr->paramSize = storagePtr->requestParamSize;
bufferPtr = &chanPtr->request.dataBuffer;
bufferPtr->bufAddr = storagePtr->requestDataPtr;
bufferPtr->length = storagePtr->requestDataSize;
rpcHdrPtr->dataSize = storagePtr->requestDataSize;
bufferPtr = &chanPtr->reply.paramBuffer;
bufferPtr->bufAddr = storagePtr->replyParamPtr;
bufferPtr->length = storagePtr->replyParamSize;
bufferPtr = &chanPtr->reply.dataBuffer;
bufferPtr->bufAddr = storagePtr->replyDataPtr;
bufferPtr->length = storagePtr->replyDataSize;
/*
* Reset state about the reception of replies.
*/
chanPtr->actualDataSize = 0;
chanPtr->actualParamSize = 0;
chanPtr->fragsReceived = 0;
chanPtr->fragsDelivered = 0;
}
#ifdef DEBUG
#define CHAN_TRACESIZE 1000
#define INC(ctr) { (ctr) = ((ctr) == CHAN_TRACESIZE-1) ? 0 : (ctr)+1; }
typedef struct {
RpcClientChannel *chanPtr;
char *action;
int pNum;
int serverID;
int chanNum;
} debugElem;
static debugElem debugArray[CHAN_TRACESIZE];
static int debugCtr;
#define CHAN_TRACE(channel, string) \
{ \
debugElem *ptr = &debugArray[debugCtr]; \
INC(debugCtr); \
ptr->chanPtr = (channel); \
ptr->action = string; \
ptr->chanNum = (channel)->index; \
ptr->serverID = (channel)->serverID; \
ptr->pNum = Mach_GetProcessorNumber(); \
}
#else
#define CHAN_TRACE(channel, string)
#endif
/*
*----------------------------------------------------------------------
*
* GetChannelAllocState --
*
* Get the state of channel allocation in regards to a certain server.
*
* Results:
* True if the server is marked as being congested. False otherwise.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static Boolean
GetChannelAllocState(serverID, time)
int serverID;
Timer_Ticks *time;
{
int i;
Boolean found = FALSE;
for (i = 0; i < (sizeof (serverAllocState) / sizeof (UnhappyServer)); i++) {
if (serverAllocState[i].serverID == serverID) {
found = TRUE;
break;
}
}
if (!found) {
return FALSE;
}
*time = serverAllocState[i].time;
return TRUE;
}
/*
*----------------------------------------------------------------------
*
* SetChannelAllocState--
*
* Set the state of channel allocation in regards to a certain server.
* If we've been getting "noAllocs" back from a server, we want to
* ramp down our use of it by using fewer client channels with it.
* This routine includes a master lock around it for places where
* it's called unprotected.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static ENTRY void
SetChannelAllocState(serverID, trouble)
int serverID;
Boolean trouble;
{
MASTER_LOCK(&rpcMutex);
SetChannelAllocStateInt(serverID, trouble);
MASTER_UNLOCK(&rpcMutex);
return;
}
/*
*----------------------------------------------------------------------
*
* SetChannelAllocStateInt --
*
* Set the state of channel allocation in regards to a certain server.
* If we've been getting "noAllocs" back from a server, we want to
* ramp down our use of it by using fewer client channels with it.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static void
SetChannelAllocStateInt(serverID, trouble)
int serverID;
Boolean trouble;
{
int i;
Boolean found = FALSE;
for (i = 0; i < (sizeof (serverAllocState) / sizeof (UnhappyServer)); i++) {
if (serverAllocState[i].serverID == serverID) {
/* already marked as unhappy */
found = TRUE;
break;
}
}
if (!found && !trouble) {
/* server isn't already marked as being in trouble. */
return;
}
if (!found) {
/* Server is in trouble, we need to record this. */
for (i = 0; i < (sizeof (serverAllocState) / sizeof (UnhappyServer));
i++) {
if (serverAllocState[i].serverID == -1) {
found = TRUE;
break;
}
}
if (!found) {
/* No more spaces to mark unhappy server! */
rpcCltStat.noMark++;
printf("SetChannelAllocStateInt: %s\n",
"No more room to keep track of congested servers.");
return;
}
}
if (trouble) {
Timer_GetCurrentTicks(&(serverAllocState[i].time));
if (serverAllocState[i].serverID == -1) {
rpcCltStat.newTrouble++;
} else {
rpcCltStat.moreTrouble++;
}
serverAllocState[i].serverID = serverID;
} else {
rpcCltStat.endTrouble++;
serverAllocState[i].serverID = -1;
}
return;
}
/*
*----------------------------------------------------------------------
*
* RpcChanAlloc --
*
* Allocate a channel for an RPC. A pointer to the channel is
* returned. The allocation is done on the basis of the server
* machine involved. The goal is to send a long series of RPC
* requests to the same server over the same channel. To that end a
* channel is chosen first if its cached address matches the input
* server address. Second choice is a previously unused channel,
* lastly we re-use a channel that has been used with a different server.
*
* Results:
* A pointer to the channel. This returns a good pointer, or it panics.
*
* Side effects:
* The channel is dedicated to the RPC until the caller frees
* the channel with RpcChanFree.
*
*----------------------------------------------------------------------
*/
ENTRY RpcClientChannel *
RpcChanAlloc(serverID)
int serverID; /* Server ID to base our allocation on. */
{
RpcClientChannel *chanPtr = (RpcClientChannel *) NULL;
/* The channel we allocate */
register int i; /* Index into channel table */
int firstUnused = -1; /* The first unused channel */
int firstBusy = -1; /* The first busy channel for server */
int firstFreeMatch = -1; /* The first chan free for server */
int firstFree = -1; /* The first chan used but now free */
Timer_Ticks time; /* When server channel state set. */
Timer_Ticks currentTime; /* Current ticks. */
Boolean srvCongested; /* Server is marked as congested. */
unsigned int smallestID = UINT_MAX;
MASTER_LOCK(&rpcMutex);
while (numFreeChannels < 1) {
rpcCltStat.chanWaits++;
waitForBusyChannel:
Sync_MasterWait(&freeChannels, &rpcMutex, FALSE);
}
firstUnused = -1;
firstBusy = -1;
firstFreeMatch = -1;
firstFree = -1;
srvCongested = GetChannelAllocState(serverID, &time);
if (srvCongested) {
Timer_AddIntervalToTicks(time, channelStateInterval, &time);
Timer_GetCurrentTicks(¤tTime);
}
if (srvCongested && (Timer_TickGE(time, currentTime))) {
/*
* Server is congested, so ramp down use of channels.
* If there's a channel, free or busy, for our server, use the
* lowest numbered one. If it's busy, wait till it's not. If there's
* no channel for this server, take free one.
*/
for (i=0 ; i<rpcNumChannels ; i++) {
chanPtr = rpcChannelPtrPtr[i];
if (serverID == chanPtr->serverID) {
if (chanPtr->state == CHAN_FREE) {
if (firstFreeMatch < 0) {
firstFreeMatch = i;
}
} else {
if (firstBusy < 0) {
firstBusy = i;
}
}
} else if (chanPtr->serverID == -1) {
if (firstUnused < 0) {
firstUnused = i;
}
} else {
if (chanPtr->state == CHAN_FREE) {
if (firstFree < 0) {
firstFree = i;
}
}
}
}
if (firstFreeMatch >= 0 &&
(firstBusy == -1 || firstBusy > firstFreeMatch)) {
/*
* We've found a free channel matching our server ID and there
* isn't a busy one for our server ID of lower number, so take
* this one.
*/
chanPtr = rpcChannelPtrPtr[firstFreeMatch];
goto found;
}
if (firstBusy > 0) {
/*
* There's a busy channel matching our server ID and either it's
* of lower number than a free channel matching our serverID or
* else there's no free channel matching our server ID. Wait for
* this one to free up.
*/
rpcCltStat.nackChanWait++;
goto waitForBusyChannel;
}
/*
* Otherwise, there's no free our busy channel matching our server ID
* so we'll create one in the code later down below.
*/
} else {
/*
* Server is not congested. Make sure it's marked okay, and allocate
* a channel in the regular fasion.
*/
if (srvCongested) {
/* Mark server as okay now. */
SetChannelAllocStateInt(serverID, FALSE);
}
/* use regular alloc */
for (i=0 ; i<rpcNumChannels ; i++) {
chanPtr = rpcChannelPtrPtr[i];
if (chanPtr->state == CHAN_FREE) {
if (chanPtr->serverID == -1) {
/*
* Remember the first unused channel.
*/
if (firstUnused < 0) {
firstUnused = i;
}
} else if (serverID == chanPtr->serverID) {
/*
* Agreement between the channels old server and the
* server ID. By reusing this channel we hope to give
* the server an implicit acknowledgment for the
* previous transaction.
*/
rpcCltStat.chanHits++;
CHAN_TRACE(chanPtr, "alloc channel w/ same server");
goto found;
} else if (chanPtr->requestRpcHdr.ID < smallestID) {
/*
* Keep track of the channel with the smallest rpcID
* (it's the least recently used). We'll use this one
* if we can't reuse a channel with the same server or
* find an unused channel.
*/
firstFree = i;
smallestID = chanPtr->requestRpcHdr.ID;
}
}
}
}
/*
* We didn't find an address match on a free channel, so we use
* the first previously unused channel or the first used but free channel.
*/
if (firstUnused >= 0) {
rpcCltStat.chanNew++;
chanPtr = rpcChannelPtrPtr[firstUnused];
CHAN_TRACE(chanPtr, "alloc first unused");
} else if (firstFree >= 0) {
rpcCltStat.chanReuse++;
chanPtr = rpcChannelPtrPtr[firstFree];
CHAN_TRACE(chanPtr, "alloc first free");
} else {
panic("Rpc_ChanAlloc can't find the free channel.\n");
}
chanPtr->serverID = serverID;
chanPtr->constPtr = (RpcConst *)NIL; /* Set in RpcSetup */
found:
chanPtr->state = CHAN_BUSY;
numFreeChannels--;
MASTER_UNLOCK(&rpcMutex);
return(chanPtr);
}
/*
*----------------------------------------------------------------------
*
* RpcChanFree --
*
* Free an RPC channel.
*
* Results:
* None.
*
* Side effects:
* The channel is available for other RPC calls.
*
*----------------------------------------------------------------------
*/
ENTRY void
RpcChanFree(chanPtr)
RpcClientChannel *chanPtr; /* The channel to free */
{
MASTER_LOCK(&rpcMutex);
CHAN_TRACE(chanPtr, "free channel");
if (chanPtr->state == CHAN_FREE) {
panic("Rpc_ChanFree: freeing free channel\n");
}
chanPtr->state = CHAN_FREE;
numFreeChannels++;
if (numFreeChannels == 1 || rpcChannelNegAcks) {
rpcCltStat.chanBroads++;
Sync_MasterBroadcast(&freeChannels);
}
MASTER_UNLOCK(&rpcMutex);
}
/*
*----------------------------------------------------------------------
*
* RpcChanClose --
*
* Process a close request from a server. This is called from
* RpcClientDispatch at interrupt level. If the channel is not
* busy then the interrupt handler
* needs to mark the channel as busy momentarily and send an
* ack to the server. See RpcClientDispatch for more details.
*
* Results:
* None.
*
* Side effects:
* The channel might be used to send an ack to the server.
*
*----------------------------------------------------------------------
*/
void
RpcChanClose(chanPtr,rpcHdrPtr)
register RpcClientChannel *chanPtr; /* The channel to use.*/
register RpcHdr *rpcHdrPtr; /* The Rpc header as it sits in the
* network module's buffer. The data
* in the message follows this. */
{
register RpcHdr *ackHdrPtr;
if ((chanPtr->state & CHAN_BUSY) == 0) {
MASTER_LOCK(&rpcMutex);
/*
* Check again to make sure the channel isn't busy,
* then temporarily allocate it while we issue the explicit ack.
* If a process has slipped in and allocated the channel then its
* request will serve as the acknowledgement and we can bail out.
*/
if ((chanPtr->state & CHAN_BUSY) != 0) {
MASTER_UNLOCK(&rpcMutex);
return;
}
chanPtr->state |= CHAN_BUSY;
numFreeChannels--;
MASTER_UNLOCK(&rpcMutex);
rpcCltStat.close++;
/*
* Set up and transmit the explicit acknowledgement packet.
* Note that fields that never change have already been
* set in RpcBufferInit.
*/
ackHdrPtr = &chanPtr->ackHdr;
ackHdrPtr->flags = RPC_ACK | RPC_CLOSE | RPC_SERVER;
ackHdrPtr->clientID = rpc_SpriteID;
ackHdrPtr->serverID = rpcHdrPtr->serverID;
ackHdrPtr->serverHint = rpcHdrPtr->serverHint;
ackHdrPtr->command = rpcHdrPtr->command;
ackHdrPtr->bootID = rpcBootID;
ackHdrPtr->ID = rpcHdrPtr->ID;
(void)RpcOutput(rpcHdrPtr->serverID, &chanPtr->ackHdr, &chanPtr->ack,
(RpcBufferSet *)NIL, 0, (Sync_Semaphore *)NIL);
/*
* Note that the packet can linger in the network output queue,
* so we are relying on the fact that it would only be reused
* for another ack in the worst case.
*/
MASTER_LOCK(&rpcMutex);
chanPtr->state &= ~CHAN_BUSY;
numFreeChannels++;
MASTER_UNLOCK(&rpcMutex);
}
}
/*
*----------------------------------------------------------------------
*
* RpcInitServerChannelState --
*
* Initialize data about the client's view of how the servers are doing.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
RpcInitServerChannelState()
{
int i;
for (i = 0; i < (sizeof (serverAllocState) / sizeof (UnhappyServer)); i++) {
serverAllocState[i].serverID = -1;
serverAllocState[i].time = timer_TicksZeroSeconds;
}
channelStateInterval = timer_IntOneSecond * 10;
}